package v1

import (
	"context"
	"fmt"
	"gitee.com/scottq/go-framework/src/v1/log"
	"time"
)

type App struct {
	name    string
	version string
	servers []IServer
	logger  log.ILog
	debug   bool

	stopTimeout time.Duration

	log.InvokeLog
}

type AppOps func(app *App)

var WithAppOpsServer = func(server IServer) AppOps {
	return func(app *App) {
		app.servers = append(app.servers, server)
	}
}

var WithAppOpsLog = func(logger log.ILog) AppOps {
	return func(app *App) {
		app.InvokeLog.AddLogger(logger)
	}
}
var WithAppOpsStopTimeout = func(timeout time.Duration) AppOps {
	return func(app *App) {
		app.stopTimeout = timeout
	}
}

var WithAppOpsVersion = func(version string) AppOps {
	return func(app *App) {
		app.version = version
	}
}
var WithAppOpsDebug = func(debug bool) AppOps {
	return func(app *App) {
		app.debug = debug
	}
}

func NewApp(name string, ops ...AppOps) (*App, error) {
	app := &App{
		name:  name,
		debug: false,
	}

	for i, _ := range ops {
		ops[i](app)
	}

	return app, nil
}

func (app *App) Run() error {
	defer app.stop()
	app.run()

	return nil
}

func (app *App) run() {
	if len(app.servers) == 0 {
		app.Log(log.InfoLog, "no server to run")
		return
	}

	//run servers
	errCh := make(chan error)
	for _, srv := range app.servers {
		go app.runServer(srv, errCh)
	}

	//wait for running error
	ok := true
	for ok {
		select {
		case err := <-errCh:
			if err != nil {
				app.Log(log.ErrorLog, err.Error())
				return
			}
			ok = false
		default:
			//println("waiting")
		}
	}
}

func (app *App) runServer(server IServer, ch chan<- error) {
	err := server.Run()
	if err != nil {
		ch <- fmt.Errorf("start server[%s] error %s", server.Name(), err)
		return
	}
	return
}

func (app *App) Stop() {
	app.stop()
}

func (app *App) stop() {
	if len(app.servers) == 0 {
		return
	}

	//stop other server
	for _, srv := range app.servers {
		ctx, cancel := context.WithTimeout(context.Background(), app.stopTimeout)
		go func() {
			defer cancel()
			err := app.stopServer(srv)
			if err != nil {
				app.Log(log.ErrorLog, err.Error())
			}
		}()

		ok := true
		for ok {
			select {
			case <-ctx.Done():
				err := ctx.Err()
				reason := ""
				if err == context.DeadlineExceeded {
					reason = "stop timeout"
				} else if err == context.Canceled {
					reason = "success"
				}
				app.Log(log.InfoLog, fmt.Sprintf("server[%s] done[%s]", srv.Name(), reason))

				ok = false
			}
		}
	}

	return
}

func (app *App) stopServer(server IServer) error {
	err := server.Stop()
	if err != nil {
		app.Log(log.ErrorLog, fmt.Sprintf("stop server[%s] error", server.Name()))
		return err
	}
	return nil
}
